#!/bin/sh
#**************************************************************************
#*                                                                        *
#*                                 OCaml                                  *
#*                                                                        *
#*      Damien Doligez, Xavier Leroy, projet Gallium, INRIA Paris         *
#*                                                                        *
#*   Copyright 2018 Institut National de Recherche en Informatique et     *
#*     en Automatique.                                                    *
#*                                                                        *
#*   All rights reserved.  This file is distributed under the terms of    *
#*   the GNU Lesser General Public License version 2.1, with the          *
#*   special exception on linking described in the file LICENSE.          *
#*                                                                        *
#**************************************************************************

# This script is run on Inria's continuous-integration servers to recompile
# from scratch, adding more run-time checks ("sanitizers") to the C code,
# and run the test suite.

# In this context, it is necessary to skip a few tests whose behaviour
# is modified by the instrumentation:

export OCAMLTEST_SKIP_TESTS="tests/afl-instrumentation/afltest.ml \
tests/afl-instrumentation/afl-fuzz-test.ml \
tests/runtime-errors/stackoverflow.ml \
tests/native-debugger/linux-gdb-amd64.ml \
tests/native-debugger/linux-lldb-amd64.ml \
tests/native-debugger/linux-gdb-arm64.ml \
tests/native-debugger/linux-lldb-arm64.ml \
tests/native-debugger/linux-gdb-riscv.ml"

jobs=-j8
make=make

#########################################################################

# Print each command before its execution
set -x

# stop on error
set -e

# Tell gcc to use only ASCII in its diagnostic outputs.
export LC_ALL=C

# How to run the test suite
if test -n "$jobs" && test -x /usr/bin/parallel; then
  export PARALLEL="$jobs $PARALLEL"
  run_testsuite="$make -C testsuite parallel"
else
  run_testsuite="$make -C testsuite all"
fi

# Figure out which version of llvm/clang to use
llvm_version=$(clang -dumpversion | cut -d . -f 1)
clang=clang-${llvm_version}
llvm_bin_dir=/usr/lib/llvm-${llvm_version}/bin

# A tool that makes error backtraces nicer
# Need to pick the one that matches clang's version and is named
# "llvm-symbolizer" (/usr/bin/llvm-symbolizer-xx doesn't work,
# that would be too easy)
export ASAN_SYMBOLIZER_PATH=${llvm_bin_dir}/llvm-symbolizer
export TSAN_SYMBOLIZER_PATH="$ASAN_SYMBOLIZER_PATH"

#########################################################################

echo "======== clang ${llvm_version}, address sanitizer, UB sanitizer ========"

git clean -q -f -d -x

# These are the undefined behaviors we want to check
# Others occur on purpose e.g. signed arithmetic overflow
ubsan="\
bool,\
builtin,\
bounds,\
enum,\
nonnull-attribute,\
nullability,\
object-size,\
pointer-overflow,\
returns-nonnull-attribute,\
shift-exponent,\
unreachable"

# Select address sanitizer and UB sanitizer, with trap-on-error behavior
sanitizers="-fsanitize=address -fsanitize=$ubsan -fno-sanitize-recover=all"
export UBSAN_OPTIONS="print_stacktrace=1"
# Don't optimize too much to get better backtraces of errors
CFLAGS="-Og -g -fno-omit-frame-pointer $sanitizers"
LDFLAGS="$sanitizers -Og -g"
CC="$clang -Og -g"

# Test that UBSAN works
cat >ubsan.c <<EOF
#include <stdbool.h>
#include <string.h>

int main(int argc, char **argv) {
  int x = 100;
  bool b;
  memcpy(&b, &x, sizeof(b));

  return b;
}
EOF

$CC $CFLAGS -c ubsan.c
$CC $LDFLAGS ubsan.o -o ubsan

./ubsan && exit 2
test $? -eq 1
rm -f ubsan ubsan.o ubsan.c

# Test that ASAN works
cat >asan.c <<EOF
#include <stdlib.h>

int main(int argc, char **argv) {
  char* x = malloc(4);
  free(x);
  free(x);
  return x[argc];
}
EOF

$CC $CFLAGS -c asan.c
$CC $LDFLAGS asan.o -o asan
./asan && exit 2
test $? -eq 1
rm -f asan asan.o asan.c

./configure \
  CC="$CC" \
  CFLAGS="$CFLAGS" \
  LDFLAGS="$LDFLAGS" \
  --disable-stdlib-manpages --enable-dependency-generation

# Build the system.  We want to check for memory leaks, hence
# 1- force ocamlrun to free memory before exiting
# 2- add an exception for ocamlyacc, which doesn't free memory
#OCAMLRUNPARAM="c=1" \
#LSAN_OPTIONS="suppressions=$(pwd)/tools/ci/inria/sanitizers/lsan-suppr.txt" \
#make $jobs
# TEMPORARY: cleanup-at-exit mode is broken in 5.0, so turn off leak
# detection entirely
ASAN_OPTIONS="detect_leaks=0,use_sigaltstack=0" make $jobs

# Run the testsuite.
# We deactivate leak detection for two reasons:
# - The suppressed leak detections related to ocamlyacc mess up the
# output of the tests and are reported as failures by ocamltest.
# - The Ocaml runtime does not free the memory when a fatal error
# occurs.

# We already use sigaltstack for signal handling. Our use might
# interact with ASAN's. Hence, we tell ASAN not to use it.

ASAN_OPTIONS="detect_leaks=0,use_sigaltstack=0" $run_testsuite

#########################################################################

# Run the testsuite with ThreadSanitizer support (--enable-tsan) enabled.
# Initially intended to detect data races in OCaml programs and C stubs, it has
# proved effective at also detecting races in the runtime (see #11040).

echo "======== clang ${llvm_version}, thread sanitizer ========"

git clean -q -f -d -x

./configure \
  CC="$CC" \
  --enable-tsan \
  CPPFLAGS="-DTSAN_INSTRUMENT_ALL" \
  --disable-stdlib-manpages --enable-dependency-generation

# Build the system
make $jobs

# Run the testsuite.
TSAN_OPTIONS="" $run_testsuite

#########################################################################

# This is a failed attempt at using the memory sanitizer
# (to detect reads from uninitialized memory).
# Some alarms are reported that look like false positive
# and are impossible to debug.

# echo "======== clang ${llvm_version}, memory sanitizer ========"

# git clean -q -f -d -x

# # Use clang 6.0
# # Memory sanitizer doesn't like the static data generated by ocamlopt,
# # hence build bytecode only
# # Select memory sanitizer
# # Don't optimize at all to get better backtraces of errors

# ./configure \
#   CC="$CC" \
#   CFLAGS="-O0 -g -fno-omit-frame-pointer -fsanitize=memory" \
#   LDFLAGS="-fsanitize=memory" \
#   --disable-native-compiler
# # A tool that makes error backtraces nicer
# # Need to pick the one that matches clang-6.0
# export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-6.0/bin/llvm-symbolizer

# # Build the system (bytecode only) and test
# make $jobs
# $run_testsuite
